package org.jtrace.geometry;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/*
import com.jme.bounding.BoundingVolume.Type;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Camera.FrustumIntersect;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
*/

import org.jtrace.Hit;
import org.jtrace.Jay;
import org.jtrace.NotHit;
import org.jtrace.primitives.Point3D;
import org.jtrace.primitives.Vector3D;

/**
 * <code>Octree</code> provides an Octree implementation using TriangleBatch 
 * as triangle data source.<br>
 * There is also some intersection methods to find the desired Octree Node.
 * 
 * @author Lucas Goraieb
 */
public class Octree {

    private BoundingBox boundingBox;
    private List<Octree> children = new ArrayList<Octree>();
    private final int maxDepth;
    private int depth;

    private Mesh mesh;
    //private int[] indices;
    private int trianglesPerNode;
	
    public Octree(Mesh mesh, Point3D min, Point3D max, int trianglesPerNode, int maxDepth) {
        this.mesh = mesh;
        this.trianglesPerNode = trianglesPerNode;
        this.maxDepth = maxDepth;
        this.depth = 0;
        this.boundingBox = new BoundingBox(min, max);   
    }
    
    /*
     * Protected octree construtor, for the octree's children.
     */
    protected Octree(Octree parent, BoundingBox bb) {
    	this.mesh = parent.mesh;
    	this.trianglesPerNode = parent.getTrianglesPerNode();
        this.maxDepth = parent.maxDepth;
        this.depth = parent.depth + 1;
        this.boundingBox = bb;
    }
    
    public void build() {
    	for (Triangle triangle : mesh.getTriangles()) {
			this.addTriangle(triangle);
		}
    }
    
    public void addTriangle(Triangle triangle) {
    	if (children.isEmpty()) {
    		boundingBox.addTriangleInBounds(triangle);
    		
    		if (boundingBox.getTrianglesCount() >= trianglesPerNode && depth != maxDepth) {
    			subdivide();
    		}
    	} else {
    		for (Octree child : children) {
				if (child.getBoundingBox().isInsideBounds(triangle)) {
					child.addTriangle(triangle);
					return;
				}
			}
    	}
    }

    public Mesh getMesh() {
        return mesh;
    }

    public int getTrianglesPerNode() {
        return trianglesPerNode;
    }

    /**
     * Get node's bounding box
     * @return node's bounding box
     */
    public BoundingBox getBoundingBox() {
        return boundingBox;
    }

    /*
     * Creates one Child Node based on x,y,z coordinates
     */     
    private void newNode(int x, int y, int z) {
    	Point3D min = boundingBox.getMin();
    	Point3D max = boundingBox.getMax();
    	
    	Vector3D diagonal = new Vector3D(min, max);
    	diagonal = diagonal.divide(2.0);
    	
    	double newMinX = (max.getX() - min.getX()) / 2.0;
    	double newMinY = (max.getY() - min.getY()) / 2.0;
    	double newMinZ = (max.getZ() - min.getZ()) / 2.0;
    	
    	Point3D newMin = new Point3D(min.getX() + newMinX * x, min.getY() + newMinY * y, min.getZ() + newMinZ * z);
    	Point3D newMax = newMin.add(diagonal);
    	
    	BoundingBox bb = new BoundingBox(newMin, newMax);
    	
        Octree node = new Octree(this, bb);
        children.add(node);
        System.out.println("New Octree: " + newMin + ", " + newMax);
    }
    
    
    /*
     * Subdivide the Octree node into 8 or less
     */
    private void subdivide() {
    	System.out.println("Octree info: " + boundingBox.getMin() + ", " + boundingBox.getMax());
        newNode(0, 0, 0);
        newNode(1, 0, 0);
        newNode(0, 1, 0);
        newNode(1, 1, 0);
        newNode(0, 0, 1);
        newNode(1, 0, 1);
        newNode(0, 1, 1);
        newNode(1, 1, 1);
        System.out.println("Octree Children Count: " + children.size());
        int trianglesCount = boundingBox.getTrianglesCount();
        for (int i = 0; i < trianglesCount; i++) {
        	Triangle triangle = boundingBox.popTriangle();
        	this.addTriangle(triangle);
        }
    }
    
    public Hit hit(Jay jay) {
    	Hit hit = new NotHit();
		
    	if (children.isEmpty()) {
    		return boundingBox.hit(jay);
    	} else {
    		for (Octree child : children) {
    			if (child.getBoundingBox().isBoxHit(jay)) {
    				Hit newHit = child.hit(jay);
    			
    				if ((newHit.isHit() && !hit.isHit()) || (newHit.isHit() && hit.isHit() && newHit.getT() < hit.getT()))
    					hit = newHit;
				}
			}
    	}
    	
    	return hit;
    }
    
}